package xdu.lz.stage5_jucUtils.chapter5_lock.stampedlock;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.StampedLock;
import java.util.stream.Collectors;


/**
 * Demo2:利用StrampedLock实现乐观读
 *
 * 乐观读：读的时候认为别人不会修改，所以允许写(注意这里是在整个read函数中允许写线程抢执行权).
 * 但是一旦数据发生变化【也就是写锁被获取到了】，就停止当前这次读，自旋重新去读。
 * 一旦检测到数据没有变化,就去获取读锁 ，读取新数据。
 */
public class StampedLock2 {

    private static StampedLock lock = new StampedLock();

    private static List<Long> data = new ArrayList<>();

    private static volatile long counter = 0;

    /**
     * 这整个过程是读操作，在进入if之前 ，写线程是能够获得CPU执行权的
     * 也就是说tryOptimisticRead不会阻塞，也不会阻塞写。
     * 但是当真正进入if后，拿到readLock还是会阻塞writeLock的。
     *
     * 之所以提高了写的获取可能性，是因为在进入if之前，写获取写锁是不会被阻塞的。
     *
     * 从宏观来看，不管多少个线程调用read，只要不进入if，那么都是不会阻塞写获取执行权的
     */
    private static void read() {
        for (;;){
            //这是一个非阻塞的API，也不会阻塞写锁,如果写锁已经被占用则返回0，否则返回当前非0戳
            long stamp  = lock.tryOptimisticRead();

            //到这里如果写锁已经被拿过了，那么stamp就会被写锁修改，此时判断条件是false，就会自旋
            //如果写锁没有被拿，没有被修改过就获取读锁,真正获取读锁的时候还是会阻塞写锁的。

            if(lock.validate(stamp)){
                try {
                    //blocked api,会阻塞写锁
                    stamp = lock.readLock();
                    //key是原数据，V是转换成字符串
                    /*System.out.println(
                            data.stream().map(String::valueOf).
                                    collect(Collectors.joining("#", "R-", "!")
                                    ));*/
                    System.out.println("read lock");
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlockRead(stamp);
                }
            }

        }
    }

    private static void write() {
        for (;;){
            long stamp = -1;
            try {
                //blocked api
                stamp = lock.writeLock();
                data.add(++counter);
                System.out.println("write lock");
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlockWrite(stamp);
            }
        }
    }

    public static void main(String[] args) {
        ExecutorService service = Executors.newFixedThreadPool(100);

        Runnable readTask = StampedLock2::read;

        Runnable writeTask = StampedLock2::write;

        for (int i = 0; i < 99; i++) {
            service.submit(readTask);
        }

        service.submit(writeTask);

    }
}
